/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxChildChangeCodec
     *
     * Codec for <mxChildChange>s. This class is created and registered
     * dynamically at load time and used implicitely via <mxCodec> and
     * the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - model
     * - previous
     * - previousIndex
     * - child
     *
     * Reference Fields:
     *
     * - parent
     */
    var codec = new mxObjectCodec(
      new mxChildChange(),
      ["model", "child", "previousIndex"],
      ["parent", "previous"]
    );

    /**
     * Function: isReference
     *
     * Returns true for the child attribute if the child
     * cell had a previous parent or if we're reading the
     * child as an attribute rather than a child node, in
     * which case it's always a reference.
     */
    codec.isReference = function (obj, attr, value, isWrite) {
      if (attr == "child" && (!isWrite || obj.model.contains(obj.previous))) {
        return true;
      }

      return mxUtils.indexOf(this.idrefs, attr) >= 0;
    };

    /**
     * Function: isExcluded
     *
     * Excludes references to parent or previous if not in the model.
     */
    codec.isExcluded = function (obj, attr, value, write) {
      return (
        mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
        (write &&
          value != null &&
          (attr == "previous" || attr == "parent") &&
          !obj.model.contains(value))
      );
    };

    /**
     * Function: afterEncode
     *
     * Encodes the child recusively and adds the result
     * to the given node.
     */
    codec.afterEncode = function (enc, obj, node) {
      if (this.isReference(obj, "child", obj.child, true)) {
        // Encodes as reference (id)
        node.setAttribute("child", enc.getId(obj.child));
      } else {
        // At this point, the encoder is no longer able to know which cells
        // are new, so we have to encode the complete cell hierarchy and
        // ignore the ones that are already there at decoding time. Note:
        // This can only be resolved by moving the notify event into the
        // execute of the edit.
        enc.encodeCell(obj.child, node);
      }

      return node;
    };

    /**
     * Function: beforeDecode
     *
     * Decodes the any child nodes as using the respective
     * codec from the registry.
     */
    codec.beforeDecode = function (dec, node, obj) {
      if (
        node.firstChild != null &&
        node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT
      ) {
        // Makes sure the original node isn't modified
        node = node.cloneNode(true);

        var tmp = node.firstChild;
        obj.child = dec.decodeCell(tmp, false);

        var tmp2 = tmp.nextSibling;
        tmp.parentNode.removeChild(tmp);
        tmp = tmp2;

        while (tmp != null) {
          tmp2 = tmp.nextSibling;

          if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT) {
            // Ignores all existing cells because those do not need to
            // be re-inserted into the model. Since the encoded version
            // of these cells contains the new parent, this would leave
            // to an inconsistent state on the model (ie. a parent
            // change without a call to parentForCellChanged).
            var id = tmp.getAttribute("id");

            if (dec.lookup(id) == null) {
              dec.decodeCell(tmp);
            }
          }

          tmp.parentNode.removeChild(tmp);
          tmp = tmp2;
        }
      } else {
        var childRef = node.getAttribute("child");
        obj.child = dec.getObject(childRef);
      }

      return node;
    };

    /**
     * Function: afterDecode
     *
     * Restores object state in the child change.
     */
    codec.afterDecode = function (dec, node, obj) {
      // Cells are decoded here after a complete transaction so the previous
      // parent must be restored on the cell for the case where the cell was
      // added. This is needed for the local model to identify the cell as a
      // new cell and register the ID.
      if (obj.child != null) {
        if (
          obj.child.parent != null &&
          obj.previous != null &&
          obj.child.parent != obj.previous
        ) {
          obj.previous = obj.child.parent;
        }

        obj.child.parent = obj.previous;
        obj.previous = obj.parent;
        obj.previousIndex = obj.index;
      }

      return obj;
    };

    // Returns the codec into the registry
    return codec;
  })()
);
